package bll

import (
	"context"
	"fmt"
	"github.com/gofrs/uuid"
	"github.com/hashicorp/golang-lru/v2/expirable"
	"github.com/smartwalle/alipay/v3"
	"github.com/spf13/cast"
	pkgUtils "gitlab.local/backend/pkg/utils"
	"gitlab.local/backend/proto/dto"
	"gorm.io/gorm"
	"net/http"
	"pay/bll/infra"
	"pay/bll/infra/ali"
	"pay/bll/infra/apple"
	"pay/bll/infra/wx"
	"pay/client/grpc"
	log "pay/collector/logger"
	"pay/config"
	"pay/errors"
	"pay/event"
	"pay/model"
	"pay/model/entity"
	"pay/model/mapping"
	"pay/store"
	"pay/store/postgres"
	"pay/utils"
	"pay/utils/limiter"
	"strings"
	"time"
)

var (
	wxPay    *wx.WxPay
	aliPay   *ali.Alipay
	applePay *apple.ApplePay
)

const (
	orderServiceFee = "service_fee"
)

type feePlan struct {
	iFeePlan              store.IFeePlan
	iFeePlanGroupObject   store.IFeePlanGroupObject
	iFeePlanGroup         store.IFeePlanGroup
	iFeePlanPaymentOrder  store.IFeePlanPaymentOrder
	prepayForEbikeLimiter *expirable.LRU[int64, any] //map[int64]*limiter.Limiter
}

var FeePlan = &feePlan{
	iFeePlan:              postgres.FeePlan,
	iFeePlanGroupObject:   postgres.FeePlanGroupObject,
	iFeePlanGroup:         postgres.FeePlanGroup,
	iFeePlanPaymentOrder:  postgres.FeePlanPaymentOrder,
	prepayForEbikeLimiter: expirable.NewLRU[int64, any](5000, nil, time.Hour),
}

func init() {
	Register(FeePlan)
}

func (a *feePlan) init() func() {
	var err error

	wxPay, err = wx.NewWxPay(&config.Conf.Pay.WxPay)
	if err != nil {
		panic(err)
	}
	aliPay, err = ali.NewAlipay(&config.Conf.Pay.Alipay)
	if err != nil {
		panic(err)
	}
	applePay, err = apple.NewApplePay(&config.Conf.Pay.ApplePay)
	if err != nil {
		panic(err)
	}
	return func() {}
}

func (a *feePlan) check(ctx context.Context, id int64, f func(plan *entity.FeePlan, planGroup *entity.FeePlanGroup)) error {

	respFeePlan, err := a.iFeePlan.Find(ctx, &model.FeePlanInfoRequest{Id: id})
	if err != nil {
		log.Errorf("get fee plan err: %v", err)
		if !errors.Is(err, gorm.ErrRecordNotFound) {
			return errors.InternalServerError.Error()
		}
	}
	if respFeePlan.Id == 0 {
		return errors.FeePlanNotExist.Error()
	}

	var respPlanGroup *entity.FeePlanGroup
	if _, data, err := a.iFeePlanGroup.List(ctx, &model.FeePlanGroupListRequest{
		Index:     1,
		Size:      1,
		FeePlanId: utils.GetValuePointer(respFeePlan.Id),
	}, model.QueryOptions{
		OnlyList: true,
	}); err != nil {

		if !errors.Is(err, gorm.ErrRecordNotFound) {
			log.Errorf("get upgrade plan err: %v", err)
			return errors.InternalServerError.Error()
		}

	} else {

		if len(data) > 0 {
			respPlanGroup = data[0]
		}
	}

	if f != nil {
		f(respFeePlan, respPlanGroup)
	}

	return nil
}

// Create
func (a *feePlan) Create(ctx context.Context, in *model.FeePlanCreateRequest) error {

	var (
		err     error
		newPack = model.NewPlanPacks(in.Packs, &model.PlanPackOption{
			Type: utils.GetValuePointer(model.FeePlanType(in.Type)),
		})
	)

	if err := newPack.Check(); err != nil {
		return err
	}
	c := buildFeePlan(in)
	_, err = a.iFeePlan.Create(ctx, c)
	return err
}

// Update
func (a *feePlan) Update(ctx context.Context, in *model.FeePlanUpdateRequest) error {

	var (
		respFeePlan   *entity.FeePlan
		respPlanGroup *entity.FeePlanGroup
	)
	if err := a.check(ctx, *in.Id, func(plan *entity.FeePlan, pg *entity.FeePlanGroup) {
		respFeePlan = plan
		respPlanGroup = pg
	}); err != nil {
		return err
	}

	var (
		dict = make(map[string]interface{})
	)

	if in.Id != nil {
		dict["id"] = in.Id
	}

	if in.Name != nil {
		dict["name"] = in.Name
	}

	if in.Type != nil {
		dict["type"] = in.Type
	}

	if in.Enable != nil {
		dict["enable"] = in.Enable
	}

	if in.Packs != nil {

		// 套餐还没有套餐组绑定的时候,是可以修改套餐详情的
		newPack := model.NewPlanPacks(in.Packs, &model.PlanPackOption{
			PlanId: in.Id,
			Type:   utils.GetValuePointer(model.FeePlanType(*in.Type)),
		})
		if err := newPack.Check(); err != nil {
			return err
		}
		oldPack := model.NewPlanPacks(respFeePlan.Packs)

		if !newPack.Equal(oldPack) && respPlanGroup != nil {
			return fmt.Errorf("已有套餐组(%v %v)关联该套餐，不允许更新", respPlanGroup.Id, respPlanGroup.Name)
		}

		dict["packs"] = newPack.Bytes()
	}

	if in.CreatedAt != nil {
		dict["created_at"] = in.CreatedAt
	}

	if in.UpdatedAt != nil {
		dict["updated_at"] = in.UpdatedAt
	}

	// do other update here
	updateAt := time.Now().Unix()
	in.UpdatedAt = &updateAt
	return a.iFeePlan.Update(ctx, *in.Id, dict)
}

// Delete
func (a *feePlan) Delete(ctx context.Context, in *model.FeePlanDeleteRequest) error {

	var (
		respPlanGroup *entity.FeePlanGroup
	)
	if err := a.check(ctx, in.Id, func(plan *entity.FeePlan, pg *entity.FeePlanGroup) {
		_ = plan
		respPlanGroup = pg
	}); err != nil {
		return err
	}

	if respPlanGroup != nil {
		return fmt.Errorf("已有套餐组(%v %v)关联该套餐，不允许删除", respPlanGroup.Id, respPlanGroup.Name)
	}

	return a.iFeePlan.Delete(ctx, in.Id)
}

// List
func (a *feePlan) List(ctx context.Context, in *model.FeePlanListRequest, opts ...model.QueryOptions) (*model.FeePlanListResponse, error) {
	var (
		err   error
		total int
		list  []*entity.FeePlan
		out   = &model.FeePlanListResponse{}
	)

	if total, list, err = a.iFeePlan.List(ctx, in, opts...); err != nil {
		return nil, err
	}

	out.Total = total
	out.List = mapping.FeePlansEntityToDto(list)

	return out, nil
}

func (a *feePlan) AppPayResult(ctx context.Context, in *model.AppPayResultRequest) (*model.AppPayResultResponse, error) {

	var (
		respPaymentOrder []*entity.FeePlanPaymentOrder
		err              error
	)
	_, respPaymentOrder, err = a.iFeePlanPaymentOrder.List(ctx, &model.FeePlanPaymentOrderListRequest{TradeNo: &in.TradeNo})
	if err != nil {
		return nil, err
	}

	if len(respPaymentOrder) <= 0 {
		return nil, fmt.Errorf(errors.NotFound.String())
	}

	out := model.AppPayResultResponse(*respPaymentOrder[0])
	return &out, nil
}

// AppPrePay 服务费付费
func (a *feePlan) AppPrePayWithLimiter(ctx context.Context, in *model.AppPrePayRequest) (*model.AppPrePayResponse, error) {

	if v, ok := a.prepayForEbikeLimiter.Get(in.EbikeId); !ok {
		a.prepayForEbikeLimiter.Add(in.EbikeId, limiter.NewLimiter(1, 1))
	} else {
		if !v.(*limiter.Limiter).Allow() {
			return nil, fmt.Errorf("请求过于频繁，请等会再试")
		}
	}
	return a.AppPrePay(ctx, in)
}

func (a *feePlan) AppPrePay(ctx context.Context, in *model.AppPrePayRequest) (*model.AppPrePayResponse, error) {

	var (
		err            error
		errMessage     []string
		infoMessage    []string
		respFeePlan    *entity.FeePlan
		feePlanGroupId int64
		out            = &model.AppPrePayResponse{}
	)

	defer func() {
		var (
			logger  = log.Info
			message = infoMessage
			result  = "SUCCESS"
		)
		if errMessage != nil {
			logger = log.Error
			result = "FAILED"
			message = append(message, errMessage...)
		}

		logger(fmt.Sprintf("【 app支付请求 Request:%+v Result:%v 】%v", in, result, strings.Join(message, " ")))

	}()

	if respFeePlan, err = a.iFeePlan.Find(ctx, &model.FeePlanInfoRequest{
		Id: in.PlanId,
	}); err != nil {
		errMsg := fmt.Sprintf("获取套餐计划%v详情失败 %+v", in.PlanId, err)
		errMessage = append(errMessage, errMsg)
		return nil, err
	}

	feePlanGroupId = in.GroupId
	if in.Id > 0 {
		if _, err = a.iFeePlanGroupObject.Find(ctx, &model.FeePlanGroupObjectInfoRequest{
			Id: in.Id,
		}); err != nil {
			errMsg := fmt.Sprintf("获取套餐计划%v详情失败 %+v", in.PlanId, err)
			errMessage = append(errMessage, errMsg)
			return nil, err
		}
	}

	pack := model.NewPlanPacks(respFeePlan.Packs).Item(in.No)

	var (
		payMap = map[model.PayChannel]func() error{
			model.AliPay:   nil,
			model.WxPay:    nil,
			model.ApplePay: nil,
		}

		orderRequest = &model.FeePlanPaymentOrderCreateRequest{
			EbikeId:        in.EbikeId,
			TradeNo:        uuid.Must(uuid.NewV4()).String(),
			Description:    fmt.Sprintf("%v%v", pack.Type.String(), pack.String()),
			Money:          int(pack.Money),
			Channel:        string(in.Channel),
			FeePlanGroupId: feePlanGroupId,
			FeePlanId:      respFeePlan.Id,
			FeePlanPackNo:  in.No,
			Status:         int(model.OrderCreated),
		}
	)

	if _, ok := payMap[in.Channel]; !ok {
		errMsg := fmt.Sprintf("不支持的支付渠道:%+v", in.Channel)
		errMessage = append(errMessage, errMsg)
		return nil, fmt.Errorf("unsupport payment method")
	}

	err = a.iFeePlanPaymentOrder.ExecTransaction(ctx, func(ctx context.Context) error {

		id, e := FeePlanPaymentOrder.Create(ctx, orderRequest)
		if e != nil {
			return e
		}

		ctx, _ = context.WithTimeout(ctx, time.Minute*3)

		feePlanType := pack.Type
		switch in.Channel {
		case model.AliPay:
			fen := pkgUtils.ShiftLeft(fmt.Sprintf("%v", pack.Money), 2, 2)
			if prepayId, expireAt, err := aliPay.Prepay(ctx, orderRequest.TradeNo, orderRequest.Description, fen, feePlanType, orderServiceFee); err != nil {
				errMsg := fmt.Sprintf("支付包支付失败%+v", err)
				errMessage = append(errMessage, errMsg)
				return err
			} else {
				out.TradeNo = orderRequest.TradeNo
				out.PrepayId = prepayId
				out.Expires = expireAt
			}
			break
		case model.WxPay:
			//fen := cast.ToInt64(pkgUtils.ShiftRight(fmt.Sprintf("%v", pack.Money), 2, 0))
			fen := cast.ToInt64(pack.Money)
			if prepayId, expireAt, err := wxPay.Prepay(ctx, orderRequest.TradeNo, orderRequest.Description, fen, orderServiceFee); err != nil {
				errMsg := fmt.Sprintf("微信支付失败%+v", err)
				errMessage = append(errMessage, errMsg)
				return err
			} else {
				out.TradeNo = orderRequest.TradeNo
				out.PrepayId = prepayId
				out.Expires = expireAt
			}
			break
		case model.ApplePay:
			/*TODO*/
			break
		}

		// 支付成功,更新记录
		if err = FeePlanPaymentOrder.Update(ctx, &model.FeePlanPaymentOrderUpdateRequest{
			Id:        utils.GetValuePointer(id),
			PrepayId:  utils.GetValuePointer(out.PrepayId),
			ExpiredAt: utils.GetValuePointer(out.Expires),
		}); err != nil {
			/* 如果失败，需要将第三方平台创建的预支付订单失效*/
			return err
		}

		event.Publish(ctx, event.CreateOrder, &event.OrderEventData{
			Id:        id,
			ExpiredAt: out.Expires,
		})
		return nil
	})

	infoMessage = append(infoMessage, fmt.Sprintf("创建支付订单成功"))
	return out, err
}

// AppListFeePlan
func (a *feePlan) AppListFeePlan(ctx context.Context, in *model.AppListFeePlanRequest) (*model.AppListFeePlanResponse, error) {

	var (
		err      error
		respList = &model.FeePlanGroupObjectListResponse{}
		out      = &model.AppListFeePlanResponse{}
	)

	if respList, err = FeePlanGroupObject.List(ctx, &model.FeePlanGroupObjectListRequest{
		Index:    1,
		Size:     1,
		ObjectId: in.BikeId,
	}, model.QueryOptions{
		OnlyList:    true,
		OrderFields: model.NewOrderFields(model.NewOrderField("id", model.OrderByDESC)),
	}); err != nil {
		return nil, err
	}

	if respList != nil && len(respList.List) > 0 {
		*out = model.AppListFeePlanResponse(*respList.List[0])
	} else {
		/* 没有查询到设备绑定的套餐组，那么查询默认套餐组 */
		if _, groupList, err := a.iFeePlanGroup.List(ctx, &model.FeePlanGroupListRequest{
			Def: utils.GetValuePointer(1),
		}, model.QueryOptions{OnlyList: true}); err != nil {
			return nil, err
		} else {
			if len(groupList) > 0 {
				out = &model.AppListFeePlanResponse{
					Detail:    groupList[0].FeePlanDetail,
					GroupId:   groupList[0].Id,
					ObjectId:  *in.BikeId,
					CreatedAt: groupList[0].CreatedAt,
					UpdatedAt: groupList[0].UpdatedAt,
				}
			} else {
				return nil, fmt.Errorf("车辆未绑定套餐信息")
			}
		}
	}

	return out, nil
}

// Find
func (a *feePlan) Find(ctx context.Context, in *model.FeePlanInfoRequest) (*model.FeePlanInfo, error) {
	var (
		err  error
		data *entity.FeePlan
		out  = &model.FeePlanInfo{}
	)

	if data, err = a.iFeePlan.Find(ctx, in); err != nil {
		return nil, err
	}

	out = mapping.FeePlanEntityToDto(data)
	return out, nil
}

func (a *feePlan) ReceiveSignNotify(ctx context.Context, in *model.ReceiveSignNotifyRequest) (int, error) {

	log.Info(fmt.Sprintf("收到签约通知"))
	return 0, nil
}

// AliPayNotify
func (a *feePlan) AliPayNotify(ctx context.Context, in *model.AliPayNotifyRequest) (int, error) {

	data, err := aliPay.ParseNotifyData(in.Request)
	if err != nil {
		return http.StatusBadRequest, fmt.Errorf(fmt.Sprintf("parse request failed:%+v", err))
	}
	var (
		attach       = infra.NewAttachFromURLEncodedJson(data.PassbackParams)
		orderRequest = &model.FeePlanPaymentOrderUpdateRequest{
			TradeNo:       utils.GetValuePointer(data.OutTradeNo),
			TransactionId: utils.GetValuePointer(data.TradeNo),
			CallBackData:  utils.GetValuePointer(attach.Json()),
		}
		tradeStatusMap = map[alipay.TradeStatus]func(req *model.FeePlanPaymentOrderUpdateRequest) *model.FeePlanPaymentOrderUpdateRequest{
			alipay.TradeStatusWaitBuyerPay: func(req *model.FeePlanPaymentOrderUpdateRequest) *model.FeePlanPaymentOrderUpdateRequest {
				req.StatusDetail = (&model.StatusDetail{Status: alipay.TradeStatusWaitBuyerPay}).String()
				return req
			},
			alipay.TradeStatusSuccess: func(req *model.FeePlanPaymentOrderUpdateRequest) *model.FeePlanPaymentOrderUpdateRequest {
				req.Status = utils.GetValuePointer(int(model.OrderPayed))
				req.StatusDetail = (&model.StatusDetail{Status: alipay.TradeStatusSuccess}).String()
				return req
			},
			alipay.TradeStatusClosed: func(req *model.FeePlanPaymentOrderUpdateRequest) *model.FeePlanPaymentOrderUpdateRequest {
				//req.Status = utils.GetValuePointer(int(model.OrderExpired))
				req.StatusDetail = (&model.StatusDetail{Status: alipay.TradeStatusClosed}).String()
				return req
			},
			alipay.TradeStatusFinished: func(req *model.FeePlanPaymentOrderUpdateRequest) *model.FeePlanPaymentOrderUpdateRequest {
				//req.Status = utils.GetValuePointer(int(model.OrderPayed))
				req.StatusDetail = (&model.StatusDetail{Status: alipay.TradeStatusClosed}).String()
				return req
			},
		}
	)

	if err = a.updateFeePlanOrder(ctx, tradeStatusMap[data.TradeStatus](orderRequest)); err != nil {
		return http.StatusInternalServerError, fmt.Errorf(fmt.Sprintf("handler callback notify event failed:%+v", err))
	}

	return http.StatusOK, err
}

// WxPayNotify 处理微信支付回调
// https://developers.weixin.qq.com/miniprogram/dev/wxcloudrun/src/development/pay/callback/
func (a *feePlan) WxPayNotify(ctx context.Context, in *model.WxPayNotifyRequest) (int, error) {

	if ok, err := wxPay.ValidateSignature(in.ReqHeaders, string(in.RequestBody)); err != nil {
		return http.StatusBadRequest, err
	} else {
		if !ok {
			return http.StatusBadRequest, fmt.Errorf("invalid signature")
		}
	}

	rsText, rs, err := wxPay.Decrypt(&in.Resource)
	if err != nil {
		return http.StatusBadRequest, err
	}

	orderRequest := &model.FeePlanPaymentOrderUpdateRequest{
		TradeNo:       utils.GetValuePointer(rs.OutTradeNo),
		TransactionId: utils.GetValuePointer(rs.TransactionId),
		CallBackData:  utils.GetValuePointer(rsText),
	}
	//attach := infra.NewAttachFromJson(rs.Attach)

	switch rs.TradeState {
	case wx.PaySuccess:
		orderRequest.Status = utils.GetValuePointer(int(model.OrderPayed))
		err = a.updateFeePlanOrder(ctx, orderRequest)
	default:
		break
	}

	return http.StatusInternalServerError, err
}

func (a *feePlan) updateFeePlanOrder(ctx context.Context, orderRequest *model.FeePlanPaymentOrderUpdateRequest) error {

	var (
		respPaymentOrders []*entity.FeePlanPaymentOrder
		respPaymentOrder  *entity.FeePlanPaymentOrder
		err               error
		tradeNo           = orderRequest.TradeNo
		t                 = time.Now().Unix()
	)
	_, respPaymentOrders, err = a.iFeePlanPaymentOrder.List(ctx, &model.FeePlanPaymentOrderListRequest{
		Index:   1,
		Size:    1,
		TradeNo: tradeNo,
	}, model.QueryOptions{
		OnlyList: true,
	})
	if err != nil {
		return err
	}

	if !(respPaymentOrders != nil && len(respPaymentOrders) > 0) {
		return fmt.Errorf(fmt.Sprintf("the order:%+v that does not exist", *tradeNo))
	}

	respPaymentOrder = respPaymentOrders[0]
	inStatus := model.PayOrderStatus(respPaymentOrder.Status)

	if orderRequest.Status != nil {
		//if model.PayOrderStatus(*orderRequest.Status) == model.OrderPayed {
		if inStatus != model.OrderCreated {
			return fmt.Errorf(fmt.Sprintf("refuse to update order:%+v status due to err status:%v", *tradeNo, inStatus.String()))
		}
		//}

		if respPaymentOrder.ExpiredAt <= t {
			return fmt.Errorf(fmt.Sprintf("refuse to update order:%+v status due to err status:%v", tradeNo, model.OrderExpired.String()))
		}
	}

	var years = 1
	if respFeePlan, e2 := a.Find(ctx, &model.FeePlanInfoRequest{
		Id: respPaymentOrder.FeePlanId,
	}); e2 != nil {
		return e2
	} else {
		pp := model.NewPlanPacks(respFeePlan.Packs).Item(respPaymentOrder.FeePlanPackNo)
		if pp.Type == model.FeePlanTypeYearlyPlan {
			years = int(pp.Years)
		}
	}

	orderRequest.Id = utils.GetValuePointer(respPaymentOrder.Id)
	orderRequest.CallBackCnt = utils.GetValuePointer(respPaymentOrder.CallBackCnt + 1)
	orderRequest.PayAt = utils.GetValuePointer(t)

	return a.iFeePlanPaymentOrder.ExecTransaction(ctx, func(ctx context.Context) error {
		err = FeePlanPaymentOrder.Update(ctx, orderRequest)
		if err != nil {
			return err
		}

		ctx, _ = context.WithTimeout(ctx, time.Minute*3)
		if _, err = grpc.SimCard.CreatePrepaid(ctx, &dto.CreatePrepaidReq{
			PaymentId: respPaymentOrder.Id,
			EbikeId:   respPaymentOrder.EbikeId,
			Sn:        respPaymentOrder.EbikeSnCode,
			Source:    1,
			Years:     int32(years),
		}); err != nil {
			return err
		}

		return err
	})

}

// buildFeePlan build entity
func buildFeePlan(in *model.FeePlanCreateRequest) *entity.FeePlan {

	ety := &entity.FeePlan{}

	ety.Id = in.Id

	ety.Name = in.Name

	ety.Type = in.Type

	ety.Packs = model.NewPlanPacks(in.Packs, &model.PlanPackOption{
		PlanId: utils.GetValuePointer(in.Id),
		Type:   utils.GetValuePointer(model.FeePlanType(in.Type)),
	}).Bytes()

	ety.Enable = in.Enable

	ety.CreatedAt = time.Now().Unix()

	ety.UpdatedAt = in.UpdatedAt

	return ety
}
